/**
 * @module vim-loader
 */


import { G3dMaterial, G3d, MeshSection } from 'vim-format'
import { InstancedMeshFactory } from './instancedMeshFactory.ts'
import { G3dSubset } from './g3dSubset.ts'
import { BufferGeometry, Color, Float32BufferAttribute, Matrix4, Mesh, Object3D, Sphere, Uint32BufferAttribute, Vector3 } from 'three'

import { MaterialsTool } from '../materials/MaterialsTool.ts'
import { GeometrySubmesh } from './insertableGeometry.ts'


/**
 * Mesh factory to load a standard vim using the progressive pipeline.
 */
export class VimMeshFactory {
  readonly g3d: G3d
  private _instancedFactory: InstancedMeshFactory

  constructor(g3d: G3d) {
    this.g3d = g3d
    const materials = new G3dMaterial(g3d.materialColors)
    this._instancedFactory = new InstancedMeshFactory(materials)
  }

  /**
   * Adds all instances from subset to the scene
   */
  public getMeshesBySubset(subset: G3dSubset, addCallback: (obj3d: Object3D) => void) {
    this.getInstancedMeshes(subset.multipleInstances, addCallback)
    this.getMergedMesh(subset.singleInstance, addCallback)
  }
  /**
   * 给出单独的mesh集合
   * @param subset 
   * @param addCallback 
   */
  private getMergedMesh(subset: G3dSubset, addCallback: (obj3d: Object3D) => void) {

    const opaque = this.createMergedMesh(subset, 'opaque')

    addCallback(opaque)
    console.log('mesh', opaque,);

    const transparents = this.createMergedMesh(subset, 'transparent');
    
    addCallback(transparents)
    console.log('trans', transparents);

  }

  private createMergedMesh(
    subset: G3dSubset,
    section: MeshSection,
  ) {
    const transparent = (section === 'transparent');
    const offsets = subset.getOffsets(section)

    const indexAttribute = new Uint32BufferAttribute(
      offsets.counts.indices,
      1
    )

    const vertexAttribute = new Float32BufferAttribute(
      offsets.counts.vertices * G3d.POSITION_SIZE,
      G3d.POSITION_SIZE
    )
    
    const colorSize = transparent ? 4 : 3
    const colorAttribute = new Float32BufferAttribute(
      offsets.counts.vertices * colorSize,
      colorSize
    )



    const geometry = new BufferGeometry()
    geometry.setIndex(indexAttribute)
    geometry.setAttribute('position', vertexAttribute)
    geometry.setAttribute('color', colorAttribute)


    const materialInstances = MaterialsTool.getInstance().applySettings({
      highlight: {
        color: new Color(0x6a, 0xd2, 0xff),
        opacity: 0.5
      },
      isolation: {
        color: new Color('#4E525C'),
        opacity: 0.08
      },
      section: {
        strokeWidth: 0.01,
        strokeFalloff: 0.75,
        strokeColor: new Color(0xf6, 0xf6, 0xf6)
      },
      outline: {
        intensity: 3,
        falloff: 3,
        blur: 2,
        color: new Color(0, 1, 1)
      }

    })
    const material = transparent ? materialInstances.transparent : materialInstances.opaque
    const opaque = new Mesh(geometry, material.material)
    const count = subset.meshCount
    for (let m = 0; m < count; m++) {
      // const added = new Array<number>()
      const meshG3dIndex = subset.getSourceMesh(m)
      const subStart = this.g3d.getMeshSubmeshStart(meshG3dIndex, section)
      const subEnd = this.g3d.getMeshSubmeshEnd(meshG3dIndex, section)

      // Skip empty mesh
      if (subStart === subEnd) {

        break
      }

      // Reusable matrix and vector3 to avoid allocations
      const matrix = new Matrix4()
      const vector = new Vector3()

      // Offsets for this mesh and all its instances
      const indexOffset = offsets.getIndexOffset(m)
      const vertexOffset = offsets.getVertexOffset(m)

      // Vertex range in the full g3d
      const vertexStart = this.g3d.getMeshVertexStart(meshG3dIndex)
      const vertexEnd = this.g3d.getMeshVertexEnd(meshG3dIndex)
      const vertexCount = vertexEnd - vertexStart

      let indexOut = 0
      let vertexOut = 0
      // Iterate over all included instances for this mesh.
      const instanceCount = subset.getMeshInstanceCount(m)
      for (let instance = 0; instance < instanceCount; instance++) {
        const g3dInstance = subset.getMeshInstance(m, instance)
        matrix.fromArray(this.g3d.getInstanceMatrix(g3dInstance))

        const submesh = new GeometrySubmesh()
        submesh.instance = this.g3d.instanceNodes[g3dInstance]
        submesh.start = indexOffset + indexOut

        const mergeOffset = instance * vertexCount
        for (let sub = subStart; sub < subEnd; sub++) {
          const color = this.g3d.getSubmeshColor(sub)

          const indexStart = this.g3d.getSubmeshIndexStart(sub)
          const indexEnd = this.g3d.getSubmeshIndexEnd(sub)

          // Merge all indices for this instance
          // Color referenced indices according to current submesh
          for (let index = indexStart; index < indexEnd; index++) {
            const v = vertexOffset + mergeOffset + this.g3d.indices[index]

            indexAttribute.setX(indexOffset + indexOut, v)

            colorAttribute.setXYZ(v, color[0], color[1], color[2])
            if (colorAttribute.itemSize === 4) {
              colorAttribute.setW(index, 0.25)
            }
            indexOut++
          }
        }

        // Transform and merge vertices
        for (let vertex = vertexStart; vertex < vertexEnd; vertex++) {
          vector.fromArray(this.g3d.positions, vertex * G3d.POSITION_SIZE)
          vector.applyMatrix4(matrix)
          vertexAttribute.setXYZ(vertexOffset + vertexOut, vector.x, vector.y, vector.z)
          submesh.expandBox(vector)
          vertexOut++
        }

        submesh.end = indexOffset + indexOut
        // this.expandBox(submesh.boundingBox)
        // this.submeshes.push(submesh)
        // added.push(this.submeshes.length - 1)
      }

      // this._meshToUpdate.add(mesh)
    }
    return opaque
  }

  /**
   * 给出InstanceMesh集合
   * instanceMesh可以显著提升性能
   * @param instanceMeshSet  含有instancedMesh的集合
   * @returns 
   */
  private getInstancedMeshes(instanceMeshSet: G3dSubset, addCallback: (obj3d: Object3D) => void) {
    const count2 = instanceMeshSet.meshCount
    /**
     * 此处原本是单独添加到scene的,但是这里被添加到group了,
     * @todo 考虑到之前和现在添加到场景的不同,可能需要详细辨别其中的区别
     */
    for (let m = 0; m < count2; m++) {
      const mesh = instanceMeshSet.getSourceMesh(m)
      const instances = instanceMeshSet.getMeshInstances(m) ?? this.g3d.meshInstances[mesh]

      const opaque = this._instancedFactory.createOpaqueFromVim(
        this.g3d,
        mesh,
        instances
      )
    
      addCallback(opaque)
      const transparent = this._instancedFactory.createTransparentFromVim(
        this.g3d,
        mesh,
        instances
      )
      addCallback(transparent)
    }

  }
}
